package mylittleinvestigations;

import java.io.IOException;
import mylittleinvestigations.CaseInformation.Case;
import mylittleinvestigations.CaseInformation.CaseParseException;
import mylittleinvestigations.CaseInformation.CaseXmlWriter;

public class Condition
{
    private ConditionCriterion criterion;
    private boolean andPending;
    private boolean orPending;
    private boolean notPending;
    
    public Condition()
    {
        this.criterion = null;
        this.andPending = false;
        this.orPending = false;
        this.notPending = false;
    }
    
    public void FlagSet(String flagId) throws CaseParseException
    {
        this.AddCriterion(new FlagSetCondition(flagId));
    }
    
    public void EvidencePresent(String evidenceId) throws CaseParseException
    {
        this.AddCriterion(new EvidencePresentCondition(evidenceId));
    }
    
    public void PartnerPresent(String partnerId) throws CaseParseException
    {
        this.AddCriterion(new PartnerPresentCondition(partnerId));
    }
    
    public void ConversationLocked(String conversationId, String unlockCondition) throws CaseParseException
    {
        this.AddCriterion(new ConversationLockedCondition(conversationId, unlockCondition));
    }
    
    public void TutorialsEnabled() throws CaseParseException
    {
        this.AddCriterion(new TutorialsEnabledCondition());
    }
    
    public void And() throws CaseParseException
    {
        if (this.notPending)
        {
            throw new CaseParseException("A negation operation is already pending.");
        }
        else if (!this.andPending && !this.orPending)
        {
            this.andPending = true;
        }
        else
        {
            throw new CaseParseException("A conjoining operation is already pending.");
        }
    }
    
    public void Or() throws CaseParseException
    {
        if (this.notPending)
        {
            throw new CaseParseException("A negation operation is already pending.");
        }
        else if (!this.andPending && !this.orPending)
        {
            this.orPending = true;
        }
        else
        {
            throw new CaseParseException("A conjoining operation is already pending.");
        }
    }
    
    public void Not() throws CaseParseException
    {
        if (!this.notPending)
        {
            this.notPending = true;
        }
        else
        {
            throw new CaseParseException("A negation operation is already pending.");
        }
    }
    
    public boolean IsTrue()
    {
        return this.criterion != null ? this.criterion.IsTrue() : false;
    }
    
    private void AddCriterion(ConditionCriterion newCriterion) throws CaseParseException
    {
        if (this.notPending)
        {
            this.notPending = false;
            this.AddCriterion(new NotCondition(newCriterion));
        }
        else if (this.andPending)
        {
            this.andPending = false;
            this.criterion = new AndCondition(this.criterion, newCriterion);
        }
        else if (this.orPending)
        {
            this.orPending = false;
            this.criterion = new OrCondition(this.criterion, newCriterion);
        }
        else if (this.criterion == null)
        {
            this.criterion = newCriterion;
        }
        else
        {
            throw new CaseParseException("Cannot add a new criterion when one already exists and no conjoining operation is pending.");
        }
    }

    public Condition Clone()
    {
        Condition condition = new Condition();
        condition.criterion = this.criterion.Clone();
        return condition;
    }
    
    public void SaveToXml(CaseXmlWriter writer) throws IOException
    {
        writer.StartElement("Condition");
        
        if (this.criterion != null)
        {
            writer.StartElement("Criterion");
            this.criterion.SaveToXml(writer);
            writer.EndElement();
        }
        
        writer.EndElement();
    }
    
    private abstract class ConditionCriterion
    {
        public abstract boolean IsTrue();
        public abstract ConditionCriterion Clone();
        public abstract void SaveToXml(CaseXmlWriter writer) throws IOException;
    }
    
    private class FlagSetCondition extends ConditionCriterion
    {
        private String flagId;
        
        public FlagSetCondition(String flagId)
        {
            this.flagId = flagId;
        }
        
        @Override
        public boolean IsTrue()
        {
            return Case.getInstance().getFlagManager().IsFlagSet(this.flagId);
        }

        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("FlagSetCondition");
            writer.WriteTextElement("FlagId", this.flagId);
            writer.EndElement();
        }

        @Override
        public ConditionCriterion Clone()
        {
            return new FlagSetCondition(this.flagId);
        }
    }
    
    private class EvidencePresentCondition extends ConditionCriterion
    {
        private String evidenceId;
        
        public EvidencePresentCondition(String evidenceId)
        {
            this.evidenceId = evidenceId;
        }
        
        @Override
        public boolean IsTrue()
        {
            return Case.getInstance().getEvidenceManager().IsEvidenceWithIdVisible(this.evidenceId);
        }

        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("EvidencePresentCondition");
            writer.WriteTextElement("EvidenceId", this.evidenceId);
            writer.EndElement();
        }

        @Override
        public ConditionCriterion Clone()
        {
            return new EvidencePresentCondition(this.evidenceId);
        }
    }
    
    private class PartnerPresentCondition extends ConditionCriterion
    {
        private String partnerId;
        
        public PartnerPresentCondition(String partnerId)
        {
            this.partnerId = partnerId;
        }
        
        @Override
        public boolean IsTrue()
        {
            return this.partnerId.equals(Case.getInstance().getPartnerManager().GetCurrentPartnerId());
        }

        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("PartnerPresentCondition");
            writer.WriteTextElement("PartnerId", this.partnerId);
            writer.EndElement();
        }

        @Override
        public ConditionCriterion Clone()
        {
            return new PartnerPresentCondition(this.partnerId);
        }
    }
    
    private class ConversationLockedCondition extends ConditionCriterion
    {
        private String conversationId;
        private String unlockCondition;
        
        public ConversationLockedCondition(String conversationId, String unlockCondition )
        {
            this.conversationId = conversationId;
            this.unlockCondition = unlockCondition;
        }
        
        @Override
        public boolean IsTrue()
        {
            Conversation conversation = Case.getInstance().getContent().GetConversationFromId(this.conversationId);
            
            return conversation.getUnlockConditions().contains(this.unlockCondition) && !conversation.getClearedUnlockConditions().contains(this.unlockCondition);
        }

        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("ConversationLockedCondition");
            writer.WriteTextElement("ConversationId", this.conversationId);
            writer.WriteTextElement("UnlockCondition", this.unlockCondition);
            writer.EndElement();
        }

        @Override
        public ConditionCriterion Clone()
        {
            return new ConversationLockedCondition(this.conversationId, this.unlockCondition);
        }
    }
    
    private class TutorialsEnabledCondition extends ConditionCriterion
    {
        public TutorialsEnabledCondition()
        {
        }
        
        @Override
        public boolean IsTrue()
        {
            return true;
        }

        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("TutorialsEnabledCondition");
            writer.EndElement();
        }

        @Override
        public ConditionCriterion Clone()
        {
            return new TutorialsEnabledCondition();
        }
    }
    
    private class AndCondition extends ConditionCriterion
    {
        private ConditionCriterion firstCriterion;
        private ConditionCriterion secondCriterion;
        
        public AndCondition(ConditionCriterion firstCriterion, ConditionCriterion secondCriterion)
        {
            this.firstCriterion = firstCriterion;
            this.secondCriterion = secondCriterion;
        }
        
        @Override
        public boolean IsTrue()
        {
            return this.firstCriterion.IsTrue() && this.secondCriterion.IsTrue();
        }

        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("AndCondition");
            
            writer.StartElement("FirstCriterion");
            this.firstCriterion.SaveToXml(writer);
            writer.EndElement();
            
            writer.StartElement("SecondCriterion");
            this.secondCriterion.SaveToXml(writer);
            writer.EndElement();
            
            writer.EndElement();
        }

        @Override
        public ConditionCriterion Clone()
        {
            return new AndCondition(this.firstCriterion.Clone(), this.secondCriterion.Clone());
        }
    }
    
    private class OrCondition extends ConditionCriterion
    {
        private ConditionCriterion firstCriterion;
        private ConditionCriterion secondCriterion;
        
        public OrCondition(ConditionCriterion firstCriterion, ConditionCriterion secondCriterion)
        {
            this.firstCriterion = firstCriterion;
            this.secondCriterion = secondCriterion;
        }
        
        @Override
        public boolean IsTrue()
        {
            return this.firstCriterion.IsTrue() || this.secondCriterion.IsTrue();
        }

        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("OrCondition");
            
            writer.StartElement("FirstCriterion");
            this.firstCriterion.SaveToXml(writer);
            writer.EndElement();
            
            writer.StartElement("SecondCriterion");
            this.secondCriterion.SaveToXml(writer);
            writer.EndElement();
            
            writer.EndElement();
        }

        @Override
        public ConditionCriterion Clone()
        {
            return new OrCondition(this.firstCriterion.Clone(), this.secondCriterion.Clone());
        }
    }
    
    private class NotCondition extends ConditionCriterion
    {
        private ConditionCriterion criterion;
        
        public NotCondition(ConditionCriterion criterion)
        {
            this.criterion = criterion;
        }
        
        @Override
        public boolean IsTrue()
        {
            return !this.criterion.IsTrue();
        }

        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("NotCondition");
            
            writer.StartElement("Criterion");
            this.criterion.SaveToXml(writer);
            writer.EndElement();
            
            writer.EndElement();
        }

        @Override
        public ConditionCriterion Clone()
        {
            return new NotCondition(this.criterion.Clone());
        }
    }
}
